/*
 * Copyright (c) 2010, Katholieke Universiteit Leuven
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     * Redistributions of source code must retain the above copyright notice,
 *       this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright notice,
 *       this list of conditions and the following disclaimer in the documentation
 *       and/or other materials provided with the distribution.
 *     * Neither the name of the Katholieke Universiteit Leuven nor the names of
 *       its contributors may be used to endorse or promote products derived from
 *       this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "net.h"

#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define xstr(s) str(s)
#define str(s) #s
#define LOOCI_COMMANDS_PORT_STR xstr(LOOCI_COMMANDS_PORT)

static uint8_t seqnr = 0;

static int get_socket(char * address, char * port) {
  int status;
  struct addrinfo hints;
  struct addrinfo * servinfo;
  struct addrinfo * p;
  int sockfd;

  fprintf(stderr, "Getting socket for %s on port %s\r\n", address, port);
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_INET6; // We use IPv6
  hints.ai_socktype = SOCK_DGRAM;
  if((status = getaddrinfo(address, port, &hints, &servinfo))!=0) {
    fprintf(stderr, "getaddrinfo error: %s\r\n", gai_strerror(status));
    return -1;
  }

  for(p = servinfo; p != NULL; p = p->ai_next) {
    if((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
      continue;
    }
    if(connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
      close(sockfd);
      continue;
    }
    break;
  }

  if(p == NULL) sockfd = -1;
  
  freeaddrinfo(servinfo);
  return sockfd;

}

bool send_command(char * nodeaddr, struct command_request * request, size_t request_len, struct command_reply * reply, size_t * reply_len) {
  int socket = get_socket(nodeaddr, LOOCI_COMMANDS_PORT_STR);
  if(socket == -1) {
    fprintf(stderr, "Could not create socket!\r\n");
    return false;
  }
  request->seq_nr = ++seqnr;
  int sent = send(socket, (uint8_t*)request, request_len + sizeof(struct command_request), 0);
  fprintf(stderr, "Sent %u bytes\r\n", sent);
  int read = recv(socket, (uint8_t*)reply, (*reply_len) + sizeof(struct command_reply), 0);
  close(socket);
  if(read == -1 || reply->seq_nr != seqnr) {
    return false;
  } else {
    *reply_len = read - sizeof(struct command_reply);
    return true;
  }
}

bool convert_node_id(char * nodeid, uint8_t * dest) {
  struct addrinfo hints, *res, *p;
  int status;
  
  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_INET6;

  if ((status = getaddrinfo(nodeid, NULL, &hints, &res)) != 0) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
    return false;
  }

  for(p = res;p != NULL; p = p->ai_next) {
    if (p->ai_family == AF_INET6) {
      struct sockaddr_in6 * ipv6 = (struct sockaddr_in6 *)p->ai_addr;
      memcpy(dest, &(ipv6->sin6_addr), 16);
      break;
    }
  }
  freeaddrinfo(res);
  if(p==NULL) {
    return false;
  } else {
    return true;
  }
}


